package com.coolwar.design.singleton;

/**
 * @author Coolwar
 * <p>
 * 假设线程一执行到instance = new Singleton()这句，这里看起来是一句话，但实际上其被编译后在JVM执行的对应会变代码就发现，这句话被编译成8条汇编指令，大致做了三件事情：
 * <p>
 * 　　1）给instance实例分配内存；
 * <p>
 * 　　2）初始化instance的构造器；
 * <p>
 * 　　3）将instance对象指向分配的内存空间（注意到这步时instance就非null了）
 * <p>
 * 　　如果指令按照顺序执行倒也无妨，但JVM为了优化指令，提高程序运行效率，允许指令重排序。如此，在程序真正运行时以上指令执行顺序可能是这样的：
 * <p>
 * 　　a）给instance实例分配内存；
 * <p>
 * 　　b）将instance对象指向分配的内存空间；
 * <p>
 * 　　c）初始化instance的构造器；
 * <p>
 * 　　这时候，当线程一执行b）完毕，在执行c）之前，被切换到线程二上，这时候instance判断为非空，此时线程二直接来到return instance语句，拿走instance然后使用，接着就顺理成章地报错（对象尚未初始化）。
 * <p>
 * 　　具体来说就是synchronized虽然保证了线程的原子性（即synchronized块中的语句要么全部执行，要么一条也不执行），但单条语句编译后形成的指令并不是一个原子操作（即可能该条语句的部分指令未得到执行，就被切换到另一个线程了）。
 * <p>
 * 　　根据以上分析可知，解决这个问题的方法是：禁止指令重排序优化，即使用volatile变量。
 */
public class Singleton {

    private static volatile Singleton instance;

    public static Singleton getInstance() {
        // 线程二检测到instance不为空
        if (null == instance) {
            synchronized (Singleton.class) {
                if (null == instance) {
                    // 线程一被指令重排，先执行了赋值，但还没执行完构造函数（即未完成初始化）
                    instance = new Singleton();
                }
            }
        }
        // 后面线程二执行时将引发：对象尚未初始化错误
        return instance;
    }

    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();

    }
}